/*
 * Author: Gabriel Gravena Neves
 */

package commom;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.UUID;
import server.Slave;
import server.Test;
import server.TestSpec;
import util.Logger;
import xml.Parser;

/**
 *
 * @author gravena
 */
public class Network {
    /**
     * Default port for the Master Slave Communication
     */
    public static final int DEFAULT_PORT = 5000;
    /**
     * Default port for the slave virtual machine communication
     */
    public static final int DEFAULT_PORT_VM = 5001;

    public static final int DEFAULT_FILE_TRANSFER_PORT = 5002;

    /**
     * Sends a XML file to a host.
     * The send/receive protocol is always the same:
     *  - Sends the size of the string
     *  - Sends the XML string
     *  - Receives the size of the response
     *  - Receives the XML response
     * @param host IP of the slave
     * @param xml The XML file, into a string
     * @return The XML Response or null (in case of error)
     */
    public static String sendXML(Slave host, String xml) {
        
        try {
            /**
             * Opens a socket with de slave host and opens the data streams
             */
            Socket sock = new Socket(host.getIp(), host.getPort());
            DataOutputStream outStream = new DataOutputStream(sock.getOutputStream());
            DataInputStream inputStream = new DataInputStream(sock.getInputStream());

            /**
             * Sends the size of the XML string
             */            
            outStream.writeInt(xml.length());

            /**
             * Send the xml information to the host
             */
            outStream.write(xml.getBytes(), 0, xml.getBytes().length);

            /**
             * The other side sends the size of the response firts
            */
            int responseSize = inputStream.readInt();            

            /**
             * Them it reads the data into a buffer
            */
            byte b[] = new byte[responseSize];
            inputStream.read(b, 0, responseSize);            

            sock.close();
            return new String(b);

        } catch (UnknownHostException ex) {
            Logger.add(ex.getMessage());
            return null;
        } catch (IOException ex) {
            Logger.add(ex.getMessage());
            return null;
        }        
    }

    /**
     * Receive a XML, do some action and answers back
     * @param host
     * @param xml
     * @return
     */
    public static String receiveXML(Socket sock) {
        try {
            /**
             * Opens a socket with de master host and opens the data streams
             */                        
            DataInputStream inputStream = new DataInputStream(sock.getInputStream());

            /**
             * Receives the string size
             */
            int size = inputStream.readInt();                               
            byte b[] = new byte[size];

            /**
             * Receives the string
             */            
            inputStream.read(b, 0, size);
                                                                                                 
            return new String(b);

        } catch (UnknownHostException ex) {
            Logger.add(ex.getMessage());
            return null;
        } catch (IOException ex) {
            Logger.add(ex.getMessage());
            return null;
        }
    }

    public static boolean sendTest(Test test) {
        int i;
        ArrayList<TestSpec> testList = test.getSpecList();

        /**
         * Iterates over the test ArrayList and sends
         * a XML message TestSpec for each Virtual Machine
         * on the list (of course that the message is first
         * directed to the slave, then the slave routes the
         * XML to the VM).
         */
        for(i=0;i<testList.size();i++) {
            String xml = "<message type=\"TestSpec\">\n<UUID>" +
                    testList.get(i).getVm().getUUID() + "</UUID>\n" +
                    "<Binary>" + testList.get(i).getTask().getBinary() + "</Binary>" +
                    "<Distribution>" + testList.get(i).getTask().getDist().toString() + "(";
            
                    double paramList[] = testList.get(i).getTask().getDist().getParamList();
                    int j;
                    for(j=0;j<paramList.length-1;j++) {
                        xml += paramList[j] + ",";
                    }
                    xml += paramList[j];
            
                    xml += ")</Distribution>\n<Samples>" + testList.get(i).getTask().getSamples() + "</Samples>\n<TestID>" + test.getId() + "</TestID>" +
                    "</message>";

            Network.sendXML(testList.get(i).getSlave(), xml);
            System.out.println(xml);
        }

        /**
         * This part is a project decision,
         * wait for all the test specifications to be
         * send to start the test or allow the test
         * initiante as soon as the slave receives the
         * XML.
         */
        String xml = "<message type=\"TestStart\">\n<TestID>"
                + "id</TestID>\n</message>";

        return true;
    }
    
    /**
     * Start the specified test on all the designed slaves.
     * @param test Test object
     * @return True in case of success, false otherwise.
     */
    public static boolean startTest(Test test) {
        int i;
        ArrayList<TestSpec> testList = test.getSpecList();

        /**
         * Iterates over the test ArrayList and sends
         * a XML message TestSpec for each Virtual Machine
         * on the list (of course that the message is first
         * directed to the slave, then the slave routes the
         * XML to the VM).
         */
        for(i=0;i<testList.size();i++) {
            String xml = "<message type=\"StartTest\">" +                  
                    "<TestID>" + test.getId() + "</TestID>" +
                    "</message>";

            Network.sendXML(testList.get(i).getSlave(), xml);
            System.out.println(xml);
        }

        return true;
    }
    
    /**
     * 
     * @param b
     */
    public static void sendDiscover() {
        byte b[];
        try {
            DatagramSocket conn = new DatagramSocket();
            conn.setBroadcast(true);

            String xml = "<?xml version=\"1.0\"?>\n<message type=\"SYN\">\n<slave_info>\n<name>Test</name>\n<ipv4>127.0.0.1</ipv4>\n<memory>512</memory>\n<processor>i5</processor>\n<hd>500</hd>\n</slave_info>\n</message>\n";
            int MessageSize = xml.length();

            xml = MessageSize + xml;

            b = new byte[xml.length()];
            b = xml.getBytes();
            System.out.println(b.length);
            DatagramPacket p = new DatagramPacket(b, b.length,InetAddress.getLocalHost(),6023);
            conn.send(p);
        } catch(SocketException ex) {
            System.out.println(ex.getMessage());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     * Request the checksum of a slave. Comparing this value with the local one it's possible
     * to decide if both sides of the connection are consistent
     * @param slave Target slave
     * @return The checksum value
     */
    public static long requestCheckSum(Slave slave) {
        String xml = "<message type=\"SlaveCheckSum\"></message>";
        String responseXML = Network.sendXML(slave, xml);        
        Parser parser = new Parser(responseXML);        
        return parser.getCheckSum();
    }

    /**
     * Sets the virtual machine list of the slave with the current list on the slave side
     * @param slave Target slave
     */
    public static void getVMList(Slave slave) {
        String xml = "<message type=\"VirtualMachineList\"></message>";
        String responseXML = Network.sendXML(slave, xml);        
        Parser parser = new Parser(responseXML);
        slave.setVMList(parser.getVMList());
        slave.calculateCheckSum();
    }

    /**
     * Request the creation of a new VM in the specified slave
     * @param slave Destination of the request
     * @param memory Amount of memory in megabytes requested on the new VM
     * @return True if the creation was successful
     */
    public static boolean requestVMCreation(Slave slave, UUID id, int memory) {
        String xml = "<message type=\"CreateVM\">\n<UUID>" + id + "</UUID>\n<memory>" + memory + "</memory>\n</message>";
        String responseXML = Network.sendXML(slave, xml);
        Parser parser = new Parser(responseXML);
        if(parser.getStatus().equals("OK")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Request the desctruction of a VM
     * @param slave Slave in witch the VM is instantiated
     * @param vm Virtual Machine to be desctructed
     * @return True if the operation was successfull
     */
    public static boolean requestVMDestroy(Slave slave, VirtualMachine vm) {
        String xml = "<message type=\"DestroyVM\">\n<UUID>" + vm.getUUID() + "</UUID>\n</message>";
        String responseXML = Network.sendXML(slave, xml);
        Parser parser = new Parser(responseXML);
        if(parser.getStatus().equals("OK")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Sends a binary file to the slave. This method is a little bit different from the others.
     * It uses a extra TCP connection with the slave, where the binary file goes through.
     * @param slave Target slave
     * @param path Path to the file to be sended
     * @return True if the slave received the file successfully
     */
    public static boolean sendBinary(Slave slave, String path) {
        File file = null;
        FileInputStream inputStream = null;
        String XML = null;
        Socket sock = null;

        try {
            /* Opens the file and a read stream */
            file = new File(path);
            inputStream = new FileInputStream(file);
            
            /* Send the request to send a file */
            XML = "<message type=\"SendBinary\">\n<name>" + file.getName() + "</name>\n+<size>" + file.length() + "</size>\n</message>";
            String response = Network.sendXML(slave, XML);            
                        
            Parser parser = new Parser(response);

            /* If the slave says he can receive a file */
            if(parser.getStatus().equals("OK")) {
                /* Opens a new socket (on a specific port) to send the file */
                sock = new Socket(slave.getIp(), Network.DEFAULT_FILE_TRANSFER_PORT);
                DataOutputStream data = new DataOutputStream(sock.getOutputStream());
                                
                /* Loads the file on a byte array, thats not the smartest thing to do, this
                 * should be improved
                 */
                byte b[] = new byte[(int)file.length()]; // Pensar em formas melhores de fazer isso
                inputStream.read(b, 0, (int)file.length());

                /* Send the file over the socket */
                data.write(b, 0, (int)file.length());

                /* Release resources */
                inputStream.close();
                sock.close();
                return true;
            } else {
                inputStream.close();
                return false;
            }

        } catch (FileNotFoundException ex) {
            Logger.add(ex.getMessage());
            System.out.println(ex.getMessage());
        } catch (IOException ex) {
            Logger.add(ex.getMessage());
            System.out.println(ex.getMessage());
        }


        return false;
    }
    
    /**
     * Request a association between the master and one slave
     * @param host The target
     * @return True if the association was successfull, false otherwise
     */
    public static boolean requestAssoc(Slave host) {
        
        String xml = "<message type=\"AssocRequest\"></message>";
        String responseXML = Network.sendXML(host, xml);
        
        if(responseXML == null) {
            return false;
        }
        Parser parser = new Parser(responseXML);
        if(parser.getStatus().equals("OK")) {
            return true;
        } else {
            return false;
        }       
    }
}
